#![allow(non_snake_case)]
use crate::components::crud::data_edit::DataEdit;
use crate::components::crud::edit_row_state::EditColumn;
use crate::components::process_list::ProcessType;
use crate::components::server_data_table::TableToolbar;
use crate::components::server_error::ServerError;
use crate::components::table_edit::TableEdit;
use crate::components::{download_xlsx_file_with_url, server_data_table::ServerDataTable};
use crate::components::{json_val_to_string, ButtonProp, DataEditInner, EditRowState};
use crate::layout::menu_state::MenuState;
use crate::layout::upload::UploadExcel;
use icondata as i;
use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos_icons::*;
use serde_json::{Map, Value};
use shq_common::prelude::{
    AndOr, Column, CommonPage, DataType, FliterItem, FormProps, Operator, Position, Size,
    WhereCause,
};
use shq_web_controls::{
    use_message, ConfirmButton, Dropdown, DropdownContent, MessageOptions, MessageVariant,
    TableState,
};
use std::collections::HashMap;
use thaw_utils::ArcOneCallback;
// #[derive(Clone)]
// pub struct CustomButton {
//     pub text: String,
//     pub icon: i::Icon,
//     pub on_click: ArcOneCallback<Option<Value>>,
// }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DataRange {
    All,
    Selected,
    None,
}
impl Default for DataRange {
    fn default() -> Self {
        DataRange::None
    }
}
#[derive(Clone)]
pub struct CustomButton {
    pub text: String,
    pub icon: Option<i::Icon>,
    pub size: Option<Size>,
    pub confirm_text: Option<String>,
    pub data_range: DataRange,
    pub data_col_names: Vec<&'static str>,
    pub on_click: ArcOneCallback<Vec<Value>>,
}
impl CustomButton {
    pub fn new(
        text: String,
        icon: Option<i::Icon>,
        size: Option<Size>,
        confirm_text: Option<String>,
        data_range: DataRange,
        data_col_names: Vec<&'static str>,
        on_click: ArcOneCallback<Vec<Value>>,
    ) -> Self {
        CustomButton {
            text,
            icon,
            size,
            confirm_text,
            data_range,
            data_col_names,
            on_click,
        }
    }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DefaultButton {
    Insert,
    Update,
    Delete,
    Import(bool),
}
#[derive(Clone)]
pub enum ButtonType {
    Default(DefaultButton),
    Custom(CustomButton),
}
impl ButtonType {
    pub fn from_default(default: DefaultButton) -> Self {
        ButtonType::Default(default)
    }
    pub fn from_custom(
        text: String,
        icon: Option<i::Icon>,
        size: Option<Size>,
        confirm_text: Option<String>,
        data_range: DataRange,
        data_col_names: Vec<&'static str>,
        on_click: ArcOneCallback<Vec<Value>>,
    ) -> Self {
        let custom = CustomButton::new(
            text,
            icon,
            size,
            confirm_text,
            data_range,
            data_col_names,
            on_click,
        );
        ButtonType::Custom(custom)
    }
}
#[derive(Clone)]
pub struct ButtonList(Vec<ButtonType>);
impl ButtonList {
    pub fn new() -> Self {
        ButtonList(Vec::new())
    }
    pub fn from_crud() -> Self {
        let mut bl = ButtonList::new();
        bl.add_default(DefaultButton::Insert);
        bl.add_default(DefaultButton::Update);
        bl.add_default(DefaultButton::Delete);
        bl.add_default(DefaultButton::Import(false));
        bl
    }
    pub fn add_default(&mut self, default: DefaultButton) {
        self.0.push(ButtonType::Default(default));
    }
    pub fn add_custom(
        &mut self,
        text: String,
        icon: Option<i::Icon>,
        size: Option<Size>,
        confirm_text: Option<String>,
        data_range: DataRange,
        data_col_names: Vec<&'static str>,
        on_click: ArcOneCallback<Vec<Value>>,
    ) {
        let custom = CustomButton::new(
            text,
            icon,
            size,
            confirm_text,
            data_range,
            data_col_names,
            on_click,
        );
        self.0.push(ButtonType::Custom(custom));
    }
    pub fn clone(&self) -> Self {
        ButtonList(self.0.clone())
    }
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
    pub fn get_custom_buttons(&self) -> impl Iterator<Item = &CustomButton> {
        self.0.iter().filter_map(|b| {
            if let ButtonType::Custom(custom) = b {
                Some(custom)
            } else {
                None
            }
        })
    }
}
impl Default for ButtonList {
    fn default() -> Self {
        ButtonList::new()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum EditMode {
    Add,
    Edit,
    None,
}
impl Default for EditMode {
    fn default() -> Self {
        EditMode::None
    }
}
pub struct ToolbarParams(HashMap<String, String>);
impl ToolbarParams {
    pub fn new() -> Self {
        ToolbarParams(HashMap::new())
    }
    pub fn from(params: HashMap<String, String>) -> Self {
        ToolbarParams(params)
    }
    pub fn get(&self, key: &str) -> Option<&String> {
        self.0.get(key)
    }
    pub fn insert(&mut self, key: String, value: String) {
        self.0.insert(key, value);
    }
}
#[component]
pub fn CommonPage(
    menu_id: u32,
    #[prop(into)] conf_name: Oco<'static, str>,
    #[prop(optional, into)] custom_buttons: ButtonList,
    #[prop(optional, into)] init_filters: Signal<Vec<WhereCause>>,
    #[prop(optional)] on_row_select_change: Option<ArcOneCallback<Option<Map<String, Value>>>>,
    #[prop(optional)] on_tolbar_params_change: Option<ArcOneCallback<HashMap<String, String>>>,
    #[prop(optional, into)] is_refresh: RwSignal<bool>,
) -> impl IntoView {
    let menu_state = use_context::<RwSignal<MenuState>>().unwrap();
    let display_import: RwSignal<bool> = RwSignal::new(false);
    let edit_mode: RwSignal<EditMode> = RwSignal::new(EditMode::None);
    let proc_type: RwSignal<ProcessType> = RwSignal::new(ProcessType::None);
    let sel_row_ids: RwSignal<Vec<u64>> = RwSignal::new(Vec::new());
    let edit_row_data: RwSignal<Option<Value>> = RwSignal::new(None);
    let file_progress_list: RwSignal<HashMap<String, String>> = RwSignal::new(HashMap::new());
    let conf_name = StoredValue::new(conf_name.to_string());
    let custom_buttons = StoredValue::new(custom_buttons);
    let conf = Resource::new(
        move || conf_name.get_value(),
        move |conf_name| async move {
            let jval_res = get_page_config(menu_id, conf_name).await;
            jval_res
        },
    );
    Effect::new(move || {
        if edit_mode.get() == EditMode::None {
            is_refresh.set(true);
        }
    });
    let edit_view = move |title: &str,
                          conf_name: Option<&str>,
                          form_props: &FormProps,
                          field_props: &Vec<Column>| {
        let width = form_props.width;
        let title = Some(title.to_string());
        let position = form_props.position.clone();
        let layout = form_props.layout.clone();
        let conf_name = conf_name.map(|d| d.to_string());
        let mut edit_cols: Vec<EditColumn> = Vec::new();
        get_edit_columns(field_props, &mut edit_cols);
        view! {
            <DataEdit menu_id title position layout max_width=width conf_name edit_mode=edit_mode field_props=StoredValue::new(edit_cols) row_data=edit_row_data/>
        }
    };
    let (can_insert, can_update, can_delete, can_import, can_export) =
        menu_state.with_untracked(|ms| {
            let edit = ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Edit);
            let delete = edit || ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Delete);
            let insert = edit || ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Insert);
            let update = edit || ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Update);
            let import = edit || ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Import);
            let export = edit || ms.has_auth_type(menu_id, shq_common::prelude::AuthType::Export);
            (insert, update, delete, import, export)
        });
    let page_view = move || {
        let on_tolbar_params_change = on_tolbar_params_change.clone();
        let on_row_select_change = on_row_select_change.clone();
        match conf.get() {
            None => either_of::EitherOf3::A(view! { <p>"Loading..."</p> }),
            Some(mv_res) => match mv_res {
                Ok(conf) => {
                    let title = StoredValue::new(conf.title.clone().unwrap_or_default());
                    let is_table_edit = conf
                        .list_props
                        .as_ref()
                        .map(|lp| lp.is_table_edit)
                        .unwrap_or(false);
                    let fps = StoredValue::new(conf.field_props.clone());
                    let pk_name = fps.with_value(|fp| {
                        fp.iter()
                            .find(|c| c.is_pk)
                            .map(|c| c.name.as_ref().map(|n| n.to_string()))
                            .flatten()
                            .expect("配置中必须有主键")
                    });
                    let list_view = if let Some(list_props) = conf.list_props.clone() {
                        let list_props = StoredValue::new(list_props);
                        let set_row_date = move |row_datas: Vec<(usize, Value)>| {
                            if !row_datas.is_empty() {
                                let v = row_datas[row_datas.len() - 1].1.clone();
                                let mut ids: Vec<u64> = Vec::new();
                                for (_idx, v) in row_datas {
                                    if let Some(pk_val) = v.get(&pk_name) {
                                        if let Some(id) = pk_val.as_u64() {
                                            ids.push(id);
                                        }
                                    }
                                }
                                if let Some(on_row_select_change) = on_row_select_change.as_ref() {
                                    if let Some(mv) = v.as_object() {
                                        (on_row_select_change)(Some(mv.clone()));
                                    }
                                }
                                edit_row_data.set(Some(v));
                                sel_row_ids.set(ids);
                            } else {
                                edit_row_data.set(None);
                                sel_row_ids.set(Vec::new());
                                if let Some(on_row_select_change) = on_row_select_change.as_ref() {
                                    (on_row_select_change)(None);
                                }
                            }
                        };
                        let edit_columns: Vec<EditColumn> = conf
                            .field_props
                            .iter()
                            .filter(|p| {
                                p.edit_props
                                    .as_ref()
                                    .map_or(false, |ep| ep.is_show_in_toolbar)
                            })
                            .map(|c| c.into())
                            .collect::<Vec<_>>();
                        let on_tolbar_params_change = on_tolbar_params_change.clone();
                        let footer_show_props=conf.list_props.as_ref().map(|lp|lp.footer_show_props).unwrap_or_default();
                        Some(view! {
                            <ServerDataTable menu_id conf_name=conf_name.get_value() title=title.get_value() list_props field_props=fps init_filters=init_filters on_select_change=set_row_date footer_show_props can_export is_refresh>
                                <TableToolbar slot>
                                    <Toolbar
                                        menu_id
                                        conf_name=conf_name
                                        title=title
                                        edit_columns
                                        custom_buttons=custom_buttons
                                        edit_row_data=edit_row_data
                                        edit_mode=edit_mode
                                        sel_row_ids=sel_row_ids
                                        is_refresh=is_refresh
                                        display_import=display_import
                                        on_tolbar_params_change
                                        can_insert
                                        can_update
                                        can_delete
                                        can_import
                                    />
                                </TableToolbar>
                            </ServerDataTable>
                        })
                    } else {
                        None
                    };
                    let mut posi: Position = Position::Inner;
                    let edit_view = if let Some(form_props) = conf.form_props.as_ref() {
                        posi = form_props.position.clone();
                        let conf_name = Some(conf_name.get_value());
                        let title = title.get_value();
                        fps.with_value(|fp| {
                            Some(edit_view(
                                title.as_str(),
                                conf_name.as_deref(),
                                form_props,
                                fp,
                            ))
                        })
                    } else {
                        None
                    };
                    let mview = if list_view.is_some() && edit_view.is_some() && !is_table_edit {
                        if posi == Position::Inner {
                            either_of::EitherOf6::A(view! {
                                <div class="flex-none">
                                    {edit_view}
                                </div>
                                {list_view}
                            })
                        } else if posi == Position::Left {
                            either_of::EitherOf6::B(view! {
                                <div class="flex flex-row gap-4">
                                    <div class="flex-none">
                                        {edit_view}
                                    </div>
                                    <div class="flex-1">
                                        {list_view}
                                    </div>
                                </div>
                            })
                        } else if posi == Position::Right {
                            either_of::EitherOf6::C(view! {
                                <div class="flex flex-row gap-4">
                                    <div class="flex-1">
                                        {list_view}
                                    </div>
                                    <div class="flex-none">
                                        {edit_view}
                                    </div>
                                </div>
                            })
                        } else {
                            either_of::EitherOf6::D(view! {
                                {list_view}
                                {edit_view}
                            })
                        }
                    } else {
                        if is_table_edit {
                            either_of::EitherOf6::E(view! {
                                <TableEdit menu_id=menu_id conf_name=conf_name.get_value() custom_buttons init_filters can_update/>
                            })
                        } else {
                            either_of::EitherOf6::F(view! {
                                {list_view}
                                {edit_view}
                            })
                        }
                    };
                    either_of::EitherOf3::B(mview)
                }
                Err(err) => {
                    either_of::EitherOf3::C(view! { <ServerError err_msg=err.to_string().into()/> })
                }
            },
        }
    };
    let on_upload = move |file_name: String| {
        spawn_local(async move {
            let fname = if file_name.len() > 36 {
                file_name[36..].to_owned()
            } else {
                file_name.to_owned()
            };
            let res = import_excel_data(menu_id, conf_name.get_value(), None, file_name).await;
            match res {
                Ok(count) => file_progress_list.update(|fps| {
                    fps.insert(fname, format!("导入成功{}条数据", count));
                }),
                Err(err) => file_progress_list.update(|fps| {
                    fps.insert(fname, format!("导入失败: {}", err));
                }),
            }
        })
    };
    let import_view = move || {
        if display_import.get() {
            Some(view! {
                <dialog id="data_import_modal" class="modal modal-open">
                    <div class="modal-box overflow-hidden">
                        <button class="absolute right-2 top-2" on:click=move|_|{
                            proc_type.set(ProcessType::None);
                            display_import.set(false);
                            file_progress_list.set(HashMap::new());
                        }>
                            <Icon icon=i::AiCloseCircleOutlined width="20" height="20"/>
                        </button>
                        <h3 class="text-lg font-bold">数据导入</h3>
                        <UploadExcel file_progress_list on_upload/>
                    </div>
                </dialog>
            })
        } else {
            None
        }
    };
    view! {
        <div class="flex-1 flex flex-col m-full h-full space-y-1 overflow-hidden">
            <Suspense fallback=|| view! { <progress class="progress w-56"></progress> }>
                {page_view}
                {import_view}
            </Suspense>
        </div>
    }
}

#[component]
fn Toolbar(
    menu_id: u32,
    conf_name: StoredValue<String>,
    title: StoredValue<String>,
    edit_columns: Vec<EditColumn>,
    custom_buttons: StoredValue<ButtonList>,
    edit_row_data: RwSignal<Option<Value>>,
    edit_mode: RwSignal<EditMode>,
    sel_row_ids: RwSignal<Vec<u64>>,
    is_refresh: RwSignal<bool>,
    display_import: RwSignal<bool>,
    on_tolbar_params_change: Option<ArcOneCallback<HashMap<String, String>>>,
    can_insert: bool,
    can_update: bool,
    can_delete: bool,
    can_import: bool,
) -> impl IntoView {
    let mess = use_message();
    let table_state =
        use_context::<ReadSignal<TableState>>().expect("ReadSignal<TableState>在上下文中不存在");
    let field_props = StoredValue::new(edit_columns);
    let (toolbar_params, _) = signal(field_props.with_value(|cols| {
        let ers = EditRowState::from_columns(&cols, vec![]);
        ers
    }));
    Effect::new(move || {
        if let Some(on_tolbar_params_change) = on_tolbar_params_change.as_ref() {
            let vals = toolbar_params.with(|ers| {
                ers.iter()
                    .map(|v| (v.name.clone(), v.value.with(|jv| json_val_to_string(jv))))
                    .collect::<HashMap<_, _>>()
            });
            (on_tolbar_params_change)(vals);
        }
    });
    let button_props = vec![ButtonProp::new(
        "查询".to_string(),
        i::AiSaveOutlined,
        ArcOneCallback::new(move |vals: Vec<(String, Value)>| {
            for (k, v) in vals.into_iter() {
                table_state.with_untracked(|ts| {
                    let operator = field_props.with_value(|fp| {
                        if let Some(col) = fp.iter().find(|c| c.name == k) {
                            if col.data_type == DataType::String {
                                Operator::Like
                            } else {
                                Operator::In
                            }
                        } else {
                            Operator::In
                        }
                    });
                    let sval = json_val_to_string(&v);
                    let fi = FliterItem {
                        operator,
                        value: Some(sval),
                    };
                    let wc = WhereCause::from(k.clone(), vec![fi], AndOr::And);
                    ts.set_filter(&k, Some(wc));
                })
            }
        }),
    )];
    let custom_button_view = move |cb: CustomButton| {
        let extract_data = |d: &Value| {
            if let Some(mv) = d.as_object() {
                let mv = cb
                    .data_col_names
                    .iter()
                    .fold(Map::new(), |mut m, col_name| {
                        if let Some(v) = mv.get(*col_name) {
                            m.insert(col_name.to_string(), v.clone());
                        }
                        m
                    });
                Value::Object(mv)
            } else {
                Value::Null
            }
        };
        let ret_vals = {
            let ret_vals: Vec<Value> = match cb.data_range {
                DataRange::All => table_state.with_untracked(|ts| {
                    ts.list_data()
                        .with(|ld| ld.iter().map(|d| extract_data(d)).collect())
                }),
                DataRange::Selected => table_state.with_untracked(|ts| {
                    ts.selected_indexs().with(|idxs| {
                        idxs.iter()
                            .filter_map(|idx| {
                                ts.list_data()
                                    .with(|ld| ld.get(*idx).map(|d| extract_data(d)))
                            })
                            .collect()
                    })
                }),
                DataRange::None => Vec::new(),
            };
            StoredValue::new(ret_vals)
        };
        match cb.confirm_text {
            Some(ct)=>{
                view! {
                    <ConfirmButton text=cb.text class="btn btn-primary" message=ct
                        on_click=move|| {
                            if (cb.data_range == DataRange::Selected)&&ret_vals.with_value(|tv|tv.is_empty()) {
                                mess.create("请先选择要操作的数据行", MessageVariant::Error, MessageOptions::default());
                                return false;
                            }
                            true
                        }
                        on_ok=move|_|{
                            (cb.on_click)(ret_vals.get_value());
                        }/>
                }.into_any()
            },
            None=>{
                view! {
                    <button class="btn btn-primary" on:click=move |_| {
                        if (cb.data_range == DataRange::Selected)&&ret_vals.with_value(|tv|tv.is_empty()) {
                            mess.create("请先选择要操作的数据行", MessageVariant::Error, MessageOptions::default());
                            return;
                        }
                        (cb.on_click)(ret_vals.get_value());
                    }>
                        {if let Some(icon)=cb.icon {
                            Some(view! {<Icon icon=icon width="20" height="20"/>})
                        } else {
                            None
                        }}
                        {cb.text}
                    </button>
                }.into_any()
            }
        }
    };
    let default_button_view = move |db: DefaultButton| {
        let can_insert = can_insert && db == DefaultButton::Insert;
        let can_update = can_update && db == DefaultButton::Update;
        let can_delete = can_delete && db == DefaultButton::Delete;
        let (can_import, can_template) = if let DefaultButton::Import(flag) = db {
            (can_import, flag)
        } else {
            (false, false)
        };
        view! {
            {if can_insert {
                Some(view! {<button class="btn btn-primary" on:click=move|_|{
                    edit_row_data.set(None);
                    edit_mode.set(EditMode::Add)
                }>新增</button>})
            }else{
                None
            }}
            {if can_update {
                Some(view! {<button class="btn btn-primary" on:click=move|_|{
                    if edit_row_data.with(|d| d.is_none()) {
                        mess.create("请先选择一行数据", MessageVariant::Error, MessageOptions::default());
                        return;
                    }
                    edit_mode.set(EditMode::Edit)
                }>修改</button>})
            }else {
                None
            }}
            {if can_delete {
                Some(view! {<ConfirmButton text="删除" message="您确认要删除选中的行吗？" class="btn btn-primary"
                    on_click=move|| {
                        if sel_row_ids.with(|d| d.is_empty()) {
                            mess.create("请选择要删除行", MessageVariant::Error, MessageOptions::default());
                            return false;
                        }
                        true
                    }
                    on_ok=move|_|{
                        spawn_local(async move {
                            let res = delete_data_by_ids(menu_id,conf_name.get_value(),sel_row_ids.get()).await;
                            match res {
                                Ok(pk_id) => {
                                    is_refresh.set(true);
                                    sel_row_ids.set(Vec::new());
                                    mess.create(format!("成功删除{}条数据", pk_id), MessageVariant::Success, MessageOptions::default());
                                }
                                Err(err) => {
                                    mess.create(format!("删除数据出错:{}", err), MessageVariant::Error, MessageOptions::default());
                                }
                            }
                        })
                    }/>}
                )
            }else{
                None
            }}
            {if can_import&&can_template {
                let download_template = Action::new(move |_| async move {
                    let conf_name = conf_name.get_value();
                    let path = format!("/export_import_template/{}", conf_name);
                    let name = title.get_value();
                    if let Err(err) = download_xlsx_file_with_url(&name,"xlsx",&path, None) {
                        mess.create(err, MessageVariant::Error, MessageOptions::default());
                    }
                });
                Some(view! {
                    <Dropdown>
                        <DropdownContent slot>
                            <ul tabindex="0" class="menu bg-base-100 rounded-box z-20 w-28 p-2 shadow">
                                <li><a on:click=move|_|{download_template.dispatch(title.clone());}>下载模版</a></li>
                                <li><a on:click=move|_|display_import.set(true)>导入数据</a></li>
                            </ul>
                        </DropdownContent>
                        <div tabindex="0" role="button" class="btn btn-primary">导入<Icon icon=i::AiDownOutlined/></div>
                    </Dropdown>
                }.into_any())
            } else if can_import {
                Some(view! {<button class="btn btn-primary" on:click=move|_|{
                    display_import.set(true)
                }>导入数据</button>}.into_any())
            } else {
                None
            }}
        }.into_any()
    };
    let buttons_view = move || {
        custom_buttons.with_value(|bs| {
            if bs.0.is_empty() {
                vec![
                    default_button_view(DefaultButton::Insert),
                    default_button_view(DefaultButton::Update),
                    default_button_view(DefaultButton::Delete),
                    default_button_view(DefaultButton::Import(true)),
                ]
            } else {
                bs.0.iter()
                    .map(|b| match b {
                        ButtonType::Default(db) => default_button_view(*db),
                        ButtonType::Custom(cb) => custom_button_view(cb.clone()),
                    })
                    .collect::<Vec<_>>()
            }
        })
    };
    let field_props=Signal::derive(move||field_props.get_value());
    view! {
        <div class="flex-none navbar bg-base-100 rounded-t-lg">
            <div class="navbar-start">
                <a class="btn btn-ghost normal-case text-base">{title.get_value()}</a>
            </div>
            <div class="navbar-center hidden lg:flex">
                <DataEditInner field_props edit_row_data=toolbar_params button_props/>
            </div>
            <div class="navbar-end flex gap-4">
                {buttons_view}
            </div>
        </div>
    }
}

fn get_edit_columns(field_props: &Vec<Column>, edit_cols: &mut Vec<EditColumn>) {
    for c in field_props {
        if c.children.is_empty() {
            if c.is_pk || c.edit_props.is_none() {
                continue;
            }
            let edit_col = EditColumn::new(
                c.name.as_ref().map(|n| n.to_string()).unwrap(),
                c.data_type,
                c.title.clone().unwrap_or("".to_string()),
                c.edit_props.as_ref().map(|e| e.clone()).unwrap(),
            );
            edit_cols.push(edit_col);
        } else {
            get_edit_columns(&c.children, edit_cols);
        }
    }
}

#[server(GetPageConfig, "/api/protect")]
pub async fn get_page_config(menu_id: u32, conf_name: String) -> Result<CommonPage, ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, shq_common::prelude::AuthType::Select).await?;
    let ps = shq_pas_backend::PageService::new(&conf_name, user.user_id, menu_id);
    let res = ps.get_config().await;
    match res {
        Ok(conf) => Ok(conf),
        Err(e) => Err(ServerFnError::ServerError(e.to_string())),
    }
}

#[server(DeleteData, "/api/protect")]
pub async fn delete_data_by_ids(
    menu_id: u32,
    conf_name: String,
    ids: Vec<u64>,
) -> Result<u64, ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, shq_common::prelude::AuthType::Delete).await?;
    let ps = shq_pas_backend::PageService::new(&conf_name, user.user_id, menu_id);
    let res = ps.delete_by_ids(ids.as_slice()).await;
    match res {
        Ok(count) => Ok(count),
        Err(e) => Err(ServerFnError::ServerError(e.to_string())),
    }
}

#[server(ImportExcelData, "/api/protect")]
pub async fn import_excel_data(
    menu_id: u32,
    conf_name: String,
    paras_vals: Option<String>,
    excel_file_name: String,
) -> Result<u64, ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, shq_common::prelude::AuthType::Import).await?;
    let mut paras_map: HashMap<String, String> = HashMap::new();
    if let Some(paras_vals) = paras_vals {
        paras_map = serde_json::from_str(&paras_vals).unwrap();
    }
    let ps = shq_pas_backend::PageService::new(&conf_name, user.user_id, menu_id);
    let excel_reader = shq_common::prelude::ExcelReader::from_file_name(excel_file_name.as_str())
        .map_err(|e| ServerFnError::new(e.to_string()))?;
    let res = ps.import_excel(paras_map, &excel_reader).await;
    match res {
        Ok(count) => Ok(count),
        Err(e) => Err(ServerFnError::ServerError(e.to_string())),
    }
}
// fn get_leaf_columns(cols: &[Column], leaf_cols: &mut Vec<Column>) {
//     for c in cols {
//         if c.children.is_empty() {
//             leaf_cols.push(c.clone());
//         } else {
//             get_leaf_columns(&c.children, leaf_cols);
//         }
//     }
// }
